package pl.edu.mimuw.irs.rcp.controller.editor.simulation.part;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.eclipse.gef.EditDomain;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.ui.properties.UndoablePropertySheetEntry;
import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertySheetPage;

import pl.edu.mimuw.irs.core.model.analysis.atomic.ActivityPlace;
import pl.edu.mimuw.irs.core.model.analysis.resources.RequirementPlace;
import pl.edu.mimuw.irs.core.model.analysis.resources.ResourcePlace;
import pl.edu.mimuw.irs.core.model.design.resource.Requirement;
import pl.edu.mimuw.irs.core.model.petrinet.INode;
import pl.edu.mimuw.irs.core.model.petrinet.Net;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.simulation.Calculations;
import pl.edu.mimuw.irs.core.model.simulation.SimulationWorkflow;
import pl.edu.mimuw.irs.core.model.simulation.atomic.EventPlace;
import pl.edu.mimuw.irs.core.util.Pair;
import pl.edu.mimuw.irs.core.util.Three;
import pl.edu.mimuw.irs.rcp.view.figure.FigureWrapper;
import pl.edu.mimuw.irs.rcp.view.figure.SimulationEventPlaceShape;



/**
 * @author Jakub Rauch
 * Created on: 2009-05-28
 */
public class SimulationEventPlaceEditPart extends SimulationPlaceEditPart implements SimulationEditPart, PropertyChangeListener {

	// *** Attributes

	private EditDomain editDomain;
	private PropertySheetPage propertySheetPage;
	private ActivityPlacePropertySource propertySource;
	private ActivityPlace place;
	private SimulationWorkflow workflow;
	public EditDomain getEditDomain() {
		if (editDomain == null) editDomain = getViewer().getEditDomain();
		return editDomain;
	}
	public void setEditDomain(EditDomain editDomain) {
		this.editDomain = editDomain;
	}
	public CommandStack getCommandStack() {
		return getEditDomain().getCommandStack();
	}
	public PropertySheetPage getPropertySheetPage() {
		if (propertySheetPage == null) {
			propertySheetPage = new PropertySheetPage();
			propertySheetPage.setRootEntry(
					new UndoablePropertySheetEntry(getCommandStack()));
		}
		return propertySheetPage;
	}
	public ActivityPlacePropertySource getPropertySource() {
		if (propertySource == null) {
			propertySource = new ActivityPlacePropertySource();
		}
		return propertySource;
	}
	public ActivityPlace getPlace() {
		if (place == null) {
			INode node = getNode();
			if (node == null || !(node instanceof ActivityPlace)) return null;
			place = (ActivityPlace) node;
		}
		return place;
	}
	public SimulationWorkflow getWorkflow() {
		if (workflow == null) {
			ActivityPlace place = getPlace();
			if (place == null) return null;
			Net net = place.getNet();
			if (net == null || !(net instanceof SimulationWorkflow)) return null;
			workflow = (SimulationWorkflow) net;
		}
		return workflow;
	}
	
	// *** Methods
	
	@Override
	public void activate() {
		getWorkflow().addPropertyChangeListener(this);
		super.activate();
	}
	
	@Override
	public void deactivate() {
		getWorkflow().removePropertyChangeListener(this);
		super.deactivate();
	}
	
	protected void refreshVisuals() {
		
		FigureWrapper wrapper = (FigureWrapper)getFigure();
		SimulationEventPlaceShape placeShape = (SimulationEventPlaceShape)wrapper.getFigure();
		Place node = (Place)getModel();
		
		if (node instanceof EventPlace) {
			EventPlace eventPlace = (EventPlace) node;
			placeShape.setBusy(eventPlace.isBusy());
		}
		
		super.refreshVisuals();
	}
	
	protected NodeShape getNodeShape() {
		return NodeShape.TGEVENTPLACE;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Object getAdapter(Class key) {
		if (key == IPropertySheetPage.class)
			return getPropertySheetPage();
		else if (key == IPropertySource.class)
			return getPropertySource();
		else
			return super.getAdapter(key);
	}

	@Override
	public void propertyChange(PropertyChangeEvent arg0) {
		getPropertySource().reset();
		this.refresh();
	}
	
	// *** Inner classes
	
	public class ActivityPlacePropertySource implements IPropertySource {
		
		// *** Attributes
		
		private static final String PROPERTY_PAIRS = "properties.activity.pairs";
		
		private Map<RequirementPlace, Set<ResourcePlace>> possibilities;
		private List<Three<RequirementPlace, Integer, List<ResourcePlace>>> properties;
		private IPropertyDescriptor[] propertyDescriptors;
		
		public Map<RequirementPlace, Set<ResourcePlace>> getPossibilities() {
			if (possibilities == null) {
				possibilities = getWorkflow().getPossibilities(getPlace());
			}
			return possibilities;
		}
		public List<Three<RequirementPlace, Integer, List<ResourcePlace>>> getProperties() {
			// TODO : Should be refreshed more often than now. Currently no caching. if (properties == null) refreshModel();
			refreshModel();
			return properties;
		}
		private void reset() {
			possibilities = null;
			propertyDescriptors = null;
			properties = null;
		}
		@Override
		public IPropertyDescriptor[] getPropertyDescriptors() {
			if (propertyDescriptors == null) refreshModel();
            return propertyDescriptors;
		}
		
		// *** Constructors
		
		public ActivityPlacePropertySource() {
			
			//this.defaultName = defaultName;
			//this.defaultCost = defaultCost;
		}
	
		// *** Methods
		
		@Override
		public Object getEditableValue() {
			return null;
		}

		@Override
		public Object getPropertyValue(Object id) {
			Pair<String, Integer> pair = decode(id);
			if (PROPERTY_PAIRS.equals(pair.getKey())) {
				// XXX error handling
				return getProperties().get(pair.getValue()).getValue();
			}
			return null;
		}

		@Override
		public boolean isPropertySet(Object id) {
			Pair<String, Integer> pair = decode(id);
			if (PROPERTY_PAIRS.equals(pair.getKey())) {
				// XXX error handling
				return getProperties().contains(pair.getValue());
			}
			return false;
		}

		@Override
		public void resetPropertyValue(Object id) {
			Pair<String, Integer> pair = decode(id);
			if (PROPERTY_PAIRS.equals(pair.getKey())) {
				// XXX error handling
				setPropertyValue(id, 0);
			}
		}

		@Override
		public void setPropertyValue(Object id, Object value) {
			Pair<String, Integer> pair = decode(id);
			if (PROPERTY_PAIRS.equals(pair.getKey())) {
				// XXX error handling
				Three<RequirementPlace, Integer, List<ResourcePlace>> three = 
					getProperties().get(pair.getValue());
				three.setValue((Integer) value);
				ResourcePlace resource = three.getOther().get(three.getValue());
				getWorkflow().select(getPlace(), three.getKey(), resource);
			}
		}
		
		
		private Pair<String, Integer> decode(Object id) {
			if (!(id instanceof String)) return null;
			String property = (String) id;
			Pair<String, Integer> pair = new Pair<String, Integer>();
			if (property.startsWith(PROPERTY_PAIRS)) {
				pair.setKey(PROPERTY_PAIRS);
				pair.setValue(new Integer(
						property.replaceFirst(PROPERTY_PAIRS + ".", "")));
			}
			return pair;
		}
		
		private String encode(String name, int position) {
			return name + "." + position;
		}
		
		private void refreshModel() {
			reset();
			
			int size = getPlace().getRequirement().getRequirements().size();
			Map<Requirement, Integer> map = getPlace().getRequirement().getRequirementsMap();
			Map<RequirementPlace, Set<ResourcePlace>> selections = getWorkflow().getSelection().get(getPlace());
			properties = new ArrayList<Three<RequirementPlace, Integer, List<ResourcePlace>>>(size);
			propertyDescriptors = new IPropertyDescriptor[size];
			int i = 0;
			for (Entry<Requirement, Integer> entry : 
					map.entrySet()) {
				RequirementPlace requirementPlace = 
					getWorkflow().getRequirementsMap().get(entry.getKey());
				Set<ResourcePlace> sameSelections = null;
				if (selections != null) sameSelections = selections.get(requirementPlace);
				
				for (int j = 0; j < entry.getValue(); j++) {
					Set<ResourcePlace> possibilities = 
						getWorkflow().getPossibilities(getPlace()).get(requirementPlace);
					
					if (possibilities == null) possibilities = new HashSet<ResourcePlace>();
					/* 
					 * Creating dropdown possibilities for this requirement.
					 */
					List<ResourcePlace> resources = 
						new ArrayList<ResourcePlace>(possibilities.size() + 1);
					int k = 0;
					resources.add(k++, null);
					for (ResourcePlace resource : possibilities) {
						resources.add(k++, resource);
					}
					
					/*
					 * Adding this possibilities to the local model.
					 */
					Three<RequirementPlace, Integer, List<ResourcePlace>> three = 
						new Three<RequirementPlace, Integer, List<ResourcePlace>>(
								requirementPlace, 0, resources);
					properties.add(i, three);
					
					/*
					 * Resources prepared. Now create names for the combobox.
					 */
					String[] names = new String[resources.size()];
					k = 0;
					int chosen = 0;
					for (ResourcePlace resource : resources) {
						if (resource == null)
							names[k++] = "";
						else {
							// FIXME : Following row will cause problem when multiple requirements are present.
							if (sameSelections != null && sameSelections.contains(resource)) chosen = k;
							Calculations calculations = getWorkflow().getCalculations(
									getPlace(), requirementPlace, resource);
							names[k++] = calculations + 
									resource.getResource().getName();
						}
					}
					
					three.setValue(chosen);
					
					/*
					 * Create descriptor.
					 */
					String property = encode(PROPERTY_PAIRS, i);
					propertyDescriptors[i] = new ComboBoxPropertyDescriptor(
							property, entry.getKey().getName(), names);
					
					i++;
				}
			}
		}
	}
	
}
